PMM-15228 Improve NGINX and Auth server performance - #5658
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## PMM-15228-pmm-server-performance-metrics #5658 +/- ##
============================================================================
+ Coverage 43.49% 45.81% +2.32%
============================================================================
Files 433 549 +116
Lines 35146 46042 +10896
Branches 591 585 -6
============================================================================
+ Hits 15287 21096 +5809
- Misses 18368 22944 +4576
- Partials 1491 2002 +511
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR adds Prometheus observability to the Grafana AuthServer in pmm-managed, exposing request/cache/latency metrics and wiring the server into the Prometheus registry, plus updating the PMM Health Grafana dashboard to visualize the new signals.
Changes:
- Implemented a custom Prometheus collector in
AuthServerwith counters/gauges/histograms for auth requests, Grafana calls, cache behavior, in-flight requests, and latencies. - Registered the
AuthServercollector during pmm-managed startup so metrics are exposed automatically. - Updated the PMM Health dashboard to include panels for the new auth metrics and additional runtime/health visualizations.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| managed/services/grafana/auth_server.go | Adds Prometheus metric descriptors/state, implements prometheus.Collector, and instruments key auth/cache/DB/Grafana code paths. |
| managed/cmd/pmm-managed/main.go | Registers the AuthServer as a Prometheus collector at startup. |
| dashboards/dashboards/PMM Health/PMM_Health.json | Adds/adjusts dashboard panels and queries to surface new auth metrics and runtime health info. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
managed/services/grafana/auth_server.go:406
- The metrics label uses the raw
X-Original-Uriheader whenextractOriginalRequestfails. That header can include query strings and high-cardinality / potentially sensitive values, which is risky to expose in Prometheus labels. Use a stable, safe route label in this error path (e.g., the currentreq.URL.Pathwhich will be/auth_request).
s.incAuthRequests(req.Method, req.Header.Get("X-Original-Uri"), http.StatusBadRequest)
managed/services/grafana/auth_server.go:451
routeis recorded as the full cleaned request path (e.g./graph/api/datasources/proxy/8/in tests). That can create unbounded label cardinality and an ever-growingsync.Map(memory leak over time) when paths contain IDs or other variable segments. Consider using the matched rule prefix fromresolveRule(or another normalized route name) as theroutelabel instead of the raw path.
s.incAuthRequests(req.Method, req.URL.Path, http.StatusOK)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
managed/services/grafana/auth_server.go:258
- mAuthRequests uses the raw cleaned request path as a label and as part of the sync.Map key. For paths with variable segments (e.g. Grafana proxy routes like /graph/api/datasources/proxy//), this can create unbounded time series and unbounded in-process memory growth because entries are never evicted from the sync.Map. Consider normalizing the label (e.g., use the matched rule prefix from resolveRule / nextPrefix chain, or otherwise bucket variable segments) to keep cardinality bounded.
mAuthRequestsDesc: prom.NewDesc(
prom.BuildFQName(prometheusNamespace, prometheusSubsystem, "requests_total"),
"Total number of authentication requests.",
[]string{"method", "route", "status_code"},
nil,
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
managed/services/grafana/auth_server.go:444
routelabel is currently set to the full request path (req.URL.Path/X-Original-Uri). That can create unbounded label cardinality (IDs, arbitrary paths) and also growss.mAuthRequestswithout bound (onesync.Mapentry per unique path/method/status), which can become a memory/DoS risk over time. Consider using the matched rule prefix (fromrules/methodRules) or another bounded route identifier for the metric label instead of the raw path.
status := httpStatusForAuthError(authErr.code)
s.incAuthRequests(req.Method, req.URL.Path, status)
s.returnError(rw, status, m, l)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 45 out of 48 changed files in this pull request and generated 1 comment.
Files not reviewed (2)
- managed/services/agents/mock_limiter_test.go: Generated file
- managed/services/grafana/mock_access_control_test.go: Generated file
Suppressed comments (8)
managed/services/grafana/auth_server.go:181
- Typo in comment: "filers" -> "filters".
// encoded filers to be added as proxy headers.
managed/services/qan/client.go:184
- Typo in comment: "preasure" -> "pressure".
managed/services/grafana/auth_server.go:172 - Typo in comment: "validiness" -> "validity" (and "TTL" is typically capitalized).
This issue also appears on line 181 of the same file.
// Ttl for auth response validiness in auth cache.
build/ansible/roles/nginx/files/conf.d/pmm.conf:76
- Comment contradicts the actual
keys_zone=STATIC:1mvalue: it says 10 MB, but 1m is 1 MB.
# keys_zone=STATIC:1m - Allocates a 10 MB area in RAM called STATIC.
build/ansible/roles/nginx/files/conf.d/pmm.conf:87
- Comment contradicts the actual
max_size=128mvalue: it says the max disk footprint is 10M.
# Sets up a 1MB memory zone named 'AUTH_CACHE' and a max disk footprint of 10M.
utils/cache/cache_ttl_test.go:16
- File header mixes Apache-2.0 licensing text with an AGPL notice, which is internally inconsistent and can confuse license checks.
utils/cache/cache_ttl_bench_test.go:16 - File header mixes Apache-2.0 licensing text with an AGPL notice, which is internally inconsistent and can confuse license checks.
utils/rateLimiter/concurrencyLimiter.go:62 Release()always incrementsavailableSlots, so callingReleasemore times thanTryAcquirepermanently increases capacity beyond the configured max, which defeats the stated purpose of "limiting" concurrency. If this is intentional, the type/docs should reflect that; otherwise consider trackingmaxSlotsand clamping (or panicking) on over-release.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
@coderabbitai full review |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 10
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
managed/services/agents/registry.go (1)
268-295: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winThe duplicate-connection guard is not atomic.
Line 268 tests for an existing agent. Line 295 stores the new record. The shard lock is released between the two operations.
Two connections that carry the same agent ID can both observe
exists == false. Both then build a channel and both callSet. The last writer wins. TheAlreadyExistsresponse at Line 279 never fires, and the losing connection'spmmAgentInfobecomes unreachable through the cache. ItskickChanis never closed, sorunStateChangeHandlerexits only when the gRPC stream context ends.An atomic store-if-absent primitive on the cache closes this window. See the related finding on
unregisterat Line 378.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry.go` around lines 268 - 295, The duplicate-connection check and insertion in the agent registration flow are not atomic, allowing concurrent connections with the same ID to overwrite each other. Update the logic around the registry method containing r.agentsCache.Get and Set to use the cache’s atomic store-if-absent operation, preserving the existing AlreadyExists response and ping/kick handling for an already registered agent.
🟡 Minor comments (11)
utils/rateLimiter/concurrencyLimiter.go-39-61 (1)
39-61: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winPreserve the configured concurrency limit.
An unmatched
Releasecan create slots abovemaxSlots. This disables the configured concurrency protection. Store the configured maximum and preventReleasefrom increasing available slots above it.
utils/rateLimiter/concurrencyLimiter.go#L39-L61: retainmaxSlotsand boundRelease.utils/rateLimiter/concurrencyLimiter_test.go#L71-L83: replace the release-before-acquire expectation with an assertion that capacity does not exceed the configured limit.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/rateLimiter/concurrencyLimiter.go` around lines 39 - 61, Update utils/rateLimiter/concurrencyLimiter.go lines 39-61: have ConcurrencyLimiter retain maxSlots when NewConcurrencyLimiter initializes it, and bound Release so availableSlots never exceeds that configured maximum. Update utils/rateLimiter/concurrencyLimiter_test.go lines 71-83 by replacing the release-before-acquire expectation with an assertion that capacity remains capped at maxSlots.utils/rateLimiter/concurrencyLimiter_bench_test.go-22-55 (1)
22-55: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse
testifyassertions in the rate-limiter testsReplace direct
Fatalassertions withrequireorassertin both affected files. The repository includestestify, and nearbyutilstests use it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/rateLimiter/concurrencyLimiter_bench_test.go` around lines 22 - 55, Replace direct Fatal-based assertions in BenchmarkConcurrencyLimiter_TryAcquireRelease and BenchmarkConcurrencyLimiter_TryAcquireWhenExhausted in utils/rateLimiter/concurrencyLimiter_bench_test.go, and the corresponding assertions in utils/rateLimiter/concurrencyLimiter_test.go lines 23-110, with testify require or assert calls; add or reuse the appropriate testify import while preserving each test’s existing expectations.Source: Coding guidelines
utils/cache/cache_ttl_bench_test.go-15-16 (1)
15-16: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRemove the AGPL distribution notice from the Apache-2.0 headers.
These files declare Apache-2.0 terms and also state that the program includes an AGPL license copy. Use the Apache-2.0 Percona header only.
utils/cache/cache_ttl_bench_test.go#L15-L16: remove the AGPL distribution notice.utils/cache/cache_ttl_test.go#L15-L16: remove the AGPL distribution notice.Based on learnings, “Go files under the repository-root directories agent/, admin/, and utils/ must use the Apache-2.0 Percona license header.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/cache/cache_ttl_bench_test.go` around lines 15 - 16, Remove the AGPL distribution notice from the Apache-2.0 Percona headers in utils/cache/cache_ttl_bench_test.go lines 15-16 and utils/cache/cache_ttl_test.go lines 15-16, leaving only the standard Apache-2.0 Percona license header for both Go test files.Source: Learnings
utils/cache/cache_test.go-21-143 (1)
21-143: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUse Testify assertions consistently.
Replace direct
t.Fatalandt.Fatalfassertions withrequirefor preconditions andassertfor comparisons.
utils/cache/cache_test.go#L21-L143: import and use Testify assertion helpers.utils/cache/cache_ttl_test.go#L27-L205: replace direct assertions with Testify assertion helpers.As per coding guidelines, “Use
testify/assertandtestify/require; do not use testify suites.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/cache/cache_test.go` around lines 21 - 143, Replace direct t.Fatal and t.Fatalf assertions in utils/cache/cache_test.go lines 21-143 and utils/cache/cache_ttl_test.go lines 27-205 with testify/assert and testify/require helpers. Use require for setup or precondition checks and assert for value comparisons, importing the helpers consistently while preserving each test’s existing expectations.Source: Coding guidelines
managed/services/victoriametrics/victoriametrics.go-485-485 (1)
485-485: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winMove the comment onto its own line.
The comment sits on the closing brace of the error check. It describes the
skipExternalExporterassignment on the next line, not the brace.✏️ Proposed fix
settings, err := models.GetSettings(q) if err != nil { return nil, err - } // In HA mode, skip ExternalExporter agents if this node is not the leader + } + + // In HA mode, skip ExternalExporter agents if this node is not the leader. skipExternalExporter := !svc.haService.IsLeader()As per coding guidelines: "Do not use inline comments such as
code // comment; place comments on separate lines."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/victoriametrics/victoriametrics.go` at line 485, Move the HA-mode comment currently trailing the closing brace onto its own line immediately before the skipExternalExporter assignment it describes, leaving the error-check closing brace unannotated.Source: Coding guidelines
managed/services/agents/registry.go-252-255 (1)
252-255: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReturn a gRPC status code, and parse the version once.
authenticateat Line 348 already parsesmd.Versionand returnscodes.InvalidArgumenton failure. This second parse repeats the work. If it were reached, it would return a plain error, which the gRPC layer maps tocodes.Unknown. The same malformed input would then produce two different codes.Return the parsed version from
authenticatealongside the node, or return a status error here.🐛 Minimal correction
pmmAgentVersion, err := version.Parse(agentMD.Version) if err != nil { - return zero, fmt.Errorf("failed to parse PMM agent version %q: %w", agentMD.Version, err) + return zero, status.Errorf(codes.InvalidArgument, "Can't parse 'version' for pmm-agent with ID %q.", agentMD.ID) }As per coding guidelines: "Use
status.Error()with proper gRPC status codes for API errors, rather than ad-hoc HTTP errors in service layers."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry.go` around lines 252 - 255, Update the authentication flow around authenticate and the PMM agent version parsing so md.Version is parsed only once and the parsed version is reused when constructing the node. Propagate the parsed version alongside the authenticated node, or convert this failure to a gRPC status.Error with codes.InvalidArgument, ensuring malformed versions consistently return that status instead of codes.Unknown.Source: Coding guidelines
managed/cmd/pmm-managed/main.go-1086-1090 (1)
1086-1090: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winKeep the API pool budgets within capacity.
pmmAgentsConnectionsLimiterallows 70% andstateUpdateRateLimiterallows 80% ofapiDbMaxOpenConns. These independent limits allow 150 operations for a 100-connection pool. Use one shared budget, or set both caps so their sum is at most 100%. Change “reserve” to “cap”. The multiple state-update queries are sequential, so do not count them as simultaneous connections.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/cmd/pmm-managed/main.go` around lines 1086 - 1090, Update the API connection limits used by agents.NewStateUpdater and pmmAgentsConnectionsLimiter so their combined caps never exceed apiDbMaxOpenConns, while treating sequential state-update queries as non-concurrent. Change the state-updater comment from “reserve” to “cap” and use either a shared budget or complementary percentages totaling at most 100%.managed/services/realtimeanalytics/service.go-539-544 (1)
539-544: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winKeep the validation warning structured.
Use
l.WithError(err).Warn(...)instead of formattingerrinto the message.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/realtimeanalytics/service.go` around lines 539 - 544, Update the agent validation warning in the FindAgentByID error path to use l.WithError(err).Warn with a descriptive message, rather than interpolating err via Warnf. Preserve the existing disconnect behavior and InvalidArgument response.Source: Coding guidelines
build/ansible/roles/nginx/files/conf.d/pmm.conf-68-89 (1)
68-89: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe comments contradict the directives.
Two mismatches exist in this block:
- Line 76 states "Allocates a 10 MB area in RAM called STATIC". Line 85 declares
keys_zone=STATIC:1m.- Line 87 states "a max disk footprint of 10M". Line 89 declares
max_size=128m.Correct the comments so an operator sizing the cache is not misled.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/ansible/roles/nginx/files/conf.d/pmm.conf` around lines 68 - 89, Correct the cache sizing comments in the nginx configuration: update the STATIC keys_zone description to state 1 MB, matching keys_zone=STATIC:1m, and update the AUTH_CACHE disk-footprint description to state 128 MB, matching max_size=128m. Leave the directives unchanged.managed/services/grafana/auth_server.go-463-466 (1)
463-466: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winEngage the component logger here.
Line 464 calls the package-level
logrus.Errorf. The surrounding code uses the*logrus.Entryfields.l. Uses.lso the entry keeps thecomponentfield.🔧 Proposed fix
if len(roles) == 0 { - logrus.Errorf("User %d has no roles", userID) + s.l.Errorf("User %d has no roles", userID) return nil, fmt.Errorf("user %d has no roles", userID) }As per coding guidelines: "Use structured logging, such as
s.l.WithField("key", value).Error("message"), and pass*logrus.Entryrather than*logrus.Logger."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 463 - 466, Update the no-roles branch in the surrounding service method to replace the package-level logrus.Errorf call with the component logger entry s.l, preserving the existing message and error return while retaining the entry’s structured component field.Source: Coding guidelines
managed/services/grafana/helpers_test.go-176-199 (1)
176-199: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winOne case in
TestNextPrefixasserts nothing.Line 186 holds a single-element slice. The inner loop iterates
paths[:len(paths)-1], which is empty for that entry. The subtest runs and passes without any assertion. Add the expected chain, or remove the entry.💚 Proposed fix
- {"/v1/server/AWSInstanceCheck/..%2f..%2finventory/Services/List'"}, + {"/v1/server/AWSInstanceCheck/..%2f..%2finventory/Services/List'", "/v1/server/AWSInstanceCheck/..%2f..%2finventory/Services/"},Confirm the expected value against the
nextPrefixchain before you apply it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_test.go` around lines 176 - 199, Update the single-element `"/v1/server/AWSInstanceCheck/..%2f..%2finventory/Services/List'"` entry in `TestNextPrefix` so it contains the complete expected `nextPrefix` chain, confirming each value against the implementation; alternatively remove the entry if no chain is intended. Ensure every test case produces at least one assertion.
🧹 Nitpick comments (16)
managed/cmd/pmm-managed/main.go (2)
1211-1211: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer the reform handle for this settings lookup.
sqlInternalDBis the raw*sql.DB. The call compiles, but it bypasses the reform query logger and the Prometheus instrumentation registered at Line 940. It also carries no context. Line 602 already usesdeps.db.WithContext(ctx)for the same operation.♻️ Proposed change
- settings, err := models.GetSettings(sqlInternalDB) + settings, err := models.GetSettings(internalDB.WithContext(ctx))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/cmd/pmm-managed/main.go` at line 1211, Update the settings lookup around models.GetSettings to use the reform database handle with the current context, matching the existing deps.db.WithContext(ctx) pattern instead of passing raw sqlInternalDB. Preserve the existing error handling and settings flow.
906-971: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake it so: factor the duplicated pool parameters into one helper.
The two
models.SetupDBParamsliterals repeat fourteen identical fields. OnlyMaxIdleConnsandMaxOpenConnsdiffer. Two copies can drift apart when a field is added later.Two smaller matters travel with this change. The panic messages at Line 931 and Line 967 are identical, so a log entry cannot identify which pool failed. The comment at Line 909 contains a typo, "serviceses", and a stray tab character.
♻️ Proposed consolidation
Add a helper near the other
main.gohelpers:func newDBParams(maxIdle, maxOpen int32) models.SetupDBParams { return models.SetupDBParams{ Address: *postgresAddrF, Name: *postgresDBNameF, Username: *postgresDBUsernameF, Password: *postgresDBPasswordF, SSLMode: *postgresSSLModeF, SSLCAPath: *postgresSSLCAPathF, SSLKeyPath: *postgresSSLKeyPathF, SSLCertPath: *postgresSSLCertPathF, HANodeID: *haNodeID, HAPeers: nodes, ConnMaxLifetime: dbMaxLifeTime, ConnMaxIdleTime: dbMaxIdleTime, MaxIdleConns: maxIdle, MaxOpenConns: maxOpen, } }Then apply this diff:
- // are still able to communicate with DB and perform tasks to keep system alive + // are still able to communicate with DB and perform tasks to keep the system alive // (like update caches, fetch settings, run cleanup tasks, etc). - setupInternalDBParams := models.SetupDBParams{ - Address: *postgresAddrF, - ... - } + setupInternalDBParams := newDBParams(internalDbMaxIdleConns, internalDbMaxOpenConns) sqlInternalDB, err := models.OpenDB(setupInternalDBParams) if err != nil { - l.Panicf("Failed to connect to database: %+v", err) + l.Panicf("Failed to connect to the internal database pool: %+v", err) }Apply the equivalent change for the API pool with
l.Panicf("Failed to connect to the API database pool: %+v", err).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/cmd/pmm-managed/main.go` around lines 906 - 971, Extract the duplicated database configuration into a newDBParams helper accepting maxIdle and maxOpen, and use it for both setupInternalDBParams and setupAPIDBParams while preserving their pool-specific limits. Differentiate the failure messages so the internal pool uses its own context and the API pool reports “Failed to connect to the API database pool”. Correct the “serviceses” typo and remove the stray tab in the internal DB comment.managed/services/agents/registry_test.go (2)
120-165: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winAdd a concurrency test for repeated kicks.
These tests exercise the sequential paths well. No test drives two
Kickcalls for the same agent at the same time. That is the exact scenario in which the non-atomic read and delete inregistry.goat Line 378 panics.A test that launches several goroutines against one agent and runs under
-racewould guard the fix.💚 Suggested test
func TestRegistryKickIsSafeUnderConcurrentCalls(t *testing.T) { t.Parallel() r := NewRegistry(nil, fakeVictoriaMetricsParams{}, &fakeHAService{params: &models.HAParams{Enabled: false}}) r.agentsCache.Set("agent-1", pmmAgentInfo{id: "agent-1", kickChan: make(chan struct{})}) ctx := logger.Set(context.Background(), "test-request") var wg sync.WaitGroup for range 16 { wg.Go(func() { r.Kick(ctx, "agent-1") }) } wg.Wait() assert.EqualValues(t, 0, r.agentsCache.Size()) }As per coding guidelines: "Ensure every goroutine has a context- or
errgroup-tied exit and does not leak during shutdown; run race tests for concurrency-sensitive packages."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry_test.go` around lines 120 - 165, Add a concurrency test alongside TestRegistryKickRemovesAgentAndClosesKickChannel that launches multiple goroutines calling Registry.Kick for the same agent, waits for all calls to finish, and verifies the agent is removed without panic or race under -race. Use a synchronization mechanism compatible with the repository’s conventions and ensure every goroutine has a bounded, context- or errgroup-tied completion path.Source: Coding guidelines
167-191: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valuePrefer generated mocks for these two stubs.
mockeryis already configured for this package. The stack lists.mockery.yamlandmanaged/services/agents/mock_limiter_test.go. Generated mocks track interface changes automatically and support expectation assertions.Add
haServiceandvictoriaMetricsParamsto.mockery.yamland replace these hand-written stubs.As per coding guidelines: "Use
testify/assertandtestify/require; do not use testify suites. Generate mocks withmockeryrather than routinely hand-rolling fakes."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry_test.go` around lines 167 - 191, Add haService and victoriaMetricsParams to the configured mockery interfaces in .mockery.yaml, generate their mocks alongside mock_limiter_test.go, and update the affected tests to use the generated mocks instead of fakeHAService and fakeVictoriaMetricsParams. Preserve the existing interface behavior and use mock expectations where applicable.Source: Coding guidelines
managed/services/agents/state_test.go (1)
89-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtend coverage to the new control paths.
These tests are precise, and the SQL expectations correctly reflect the non-transactional path in
UpdateAgentsState.Four new behaviors in
state.gocarry no tests:
runStateChangeHandlerbatching and its exit onkickChanand on context cancellation.- The
stateUpdateRateLimiterrejection path and theerrStateUpdateLimitExceededbranch.- The backoff retry and reset behavior.
- The singleflight deduplication in
sendSetStateRequest.Every test here passes
maxConcurrentUpdatesof 1, so the limiter never rejects. A test that sets the limit to 1 and drives two concurrent updates would cover the rejection branch.I can draft these tests if that would help. Shall I proceed?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/state_test.go` around lines 89 - 127, Extend state updater tests beyond TestUpdateAgentsStateQueuesUpdatesForAllConnectedAgents to cover runStateChangeHandler batching and exits on kickChan/context cancellation, stateUpdateRateLimiter rejection including errStateUpdateLimitExceeded, backoff retry and reset behavior, and sendSetStateRequest singleflight deduplication. Use concurrent updates with maxConcurrentUpdates set to 1 to exercise limiter rejection, and retain SQL expectations for the non-transactional UpdateAgentsState path.managed/services/agents/state.go (1)
288-330: 🚀 Performance & Scalability | 🔵 TrivialConsider batching the per-row service lookups.
The pre-fetched node and the embedded agent version remove substantial query volume. That is a clear gain.
One pattern remains.
models.FindServiceByIDruns once per row inside this loop, andmodels.FindNodeByIDruns once per RDS exporter. A pmm-agent that monitors fifty services issues fifty sequential queries.The whole function now runs under the 5-second
stateChangeTimeoutset inrunStateChangeHandlerat Line 191. If a large agent exceeds that budget, the request fails, the backoff triggers, and the entire set is retried from the beginning. Under load such an agent may never complete a state update.Collect the service IDs in a first pass, then resolve them with one filtered query into a map. Apply the same approach to the RDS node lookups.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/state.go` around lines 288 - 330, Refactor the state-building flow around the agent iteration to collect all service IDs and RDS node IDs first, resolve them with batched filtered queries, and index the results by ID. Update the AzureDatabaseExporterType and RDSExporterType branches to reuse those maps instead of calling models.FindServiceByID or models.FindNodeByID per row, while preserving existing lookup errors and configuration behavior.managed/services/grafana/helpers_bench_test.go (2)
27-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the commented-out code, Ensign.
Lines 33-35 contain a commented-out verification block. The loop below already verifies the result. Delete the dead lines.
🧹 Proposed cleanup
b.ReportAllocs() - // cleanedPath, err := cleanPath(unescapedURI) - // require.NoError(b, err) - // require.Equal(b, expectedCleanPath, cleanedPath) - b.ResetTimer()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_bench_test.go` around lines 27 - 47, Remove the commented-out cleanPath verification block in BenchmarkCleanPath, including the commented require.NoError and require.Equal lines, while leaving the active benchmark loop and its validations unchanged.
97-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid changing the standard logger for the whole package.
logrus.SetOutput(io.Discard)mutates global state. Other tests and benchmarks in packagegrafanashare that logger. Create a local logger instead.♻️ Proposed change
- logrus.SetOutput(io.Discard) - l := logrus.NewEntry(logrus.StandardLogger()) + logger := logrus.New() + logger.SetOutput(io.Discard) + l := logrus.NewEntry(logger)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_bench_test.go` around lines 97 - 101, Update BenchmarkResolveRule to stop mutating the global standard logger via logrus.SetOutput; create a local logrus.Logger configured to discard output, then build the log entry from that local logger while preserving the benchmark’s existing behavior.managed/services/grafana/helpers_test.go (1)
264-270: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe local variable
testsshadows the imported packagetests.The file imports
github.com/percona/pmm/managed/utils/testsand uses it at line 191. Line 266 declares a local slice namedtests. The code compiles because the scopes differ, but the name reuse is confusing. Rename the local variable.♻️ Proposed rename
- tests := []struct { + testCases := []struct { path string expected string wantErr bool }{Rename the loop at line 321 as well.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_test.go` around lines 264 - 270, Rename the local test-case slice `tests` in `TestCleanPath` to avoid shadowing the imported `tests` package, and update the associated loop at line 321 to use the new name consistently.managed/services/grafana/auth_server_bench_test.go (1)
118-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
b.ReportAllocson line 118 does not apply to the subtests.Each
b.Runreceives a new*testing.B. Allocation reporting does not inherit from the parent. Move the call inside the subtest.♻️ Proposed change
- b.ReportAllocs() - for _, tc := range []struct { @@ b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() tokenSeq := 0🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_bench_test.go` around lines 118 - 131, Move b.ReportAllocs() from the parent benchmark into the b.Run subtest callback within the benchmark table loop, so allocation reporting applies to each subtest.managed/services/grafana/auth_server_test.go (2)
203-220: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThese two mocks serve no purpose.
Lines 205-211 create
candacand register cleanup assertions. Line 213 then callssetupLBACServer(t), which builds its own mocks. Neithercnoracis attached to the server under test. Delete them.🧹 Proposed cleanup
t.Run("enabled LBAC - lbacPrefixes", func(t *testing.T) { t.Parallel() - c := newMockGrafanaAuthUserGetter(t) - ac := newMockAccessControl(t) - ac.On("isEnabled").Return(true).Maybe() - t.Cleanup(func() { - c.AssertExpectations(t) - ac.AssertExpectations(t) - }) s, _, _ := setupLBACServer(t)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_test.go` around lines 203 - 220, Remove the unused c and ac mock setup, including their expectation cleanup and related isEnabled expectation, from the “enabled LBAC - lbacPrefixes” test; keep setupLBACServer(t) as the sole server initialization before exercising needAddLBACFilters.
511-527: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore or remove the commented-out cache assertions.
Line 525 holds a commented-out assertion. The same pattern appears at lines 801, 818, and 836. The helper
cacheSize(s)on line 87 gives the working equivalent. Either use it or delete the comments.🧹 Proposed change for line 525
- // assert.True(t, len(s.cache) == 0, "cache should be empty on anonymous user") + assert.Zero(t, cacheSize(s), "cache should be empty on anonymous user")Verify the expected value first.
getAuthUsercaches every positive Grafana reply, including one for an anonymous user, so the count may not be zero.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_test.go` around lines 511 - 527, Remove the commented-out cache assertions in the anonymous-user test and the matching cases around the other referenced tests, or restore them using the cacheSize(s) helper. Verify the expected cache count first, since authenticateUser caches successful getAuthUser responses even for anonymous users.managed/services/grafana/helpers.go (1)
244-282: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsolidate the duplicated header extraction, Number One.
extractAuthHeadersandgetAuthCacheKeycontain the same 8-line block that readsAuthorizationandCookie. A third copy exists inAuthServer.getAuthUserinmanaged/services/grafana/auth_server.goat lines 571-578. Extract one helper and call it from all three sites.♻️ Proposed refactor
+// authHeaderValues returns the Authorization and Cookie header values. +func authHeaderValues(req *http.Request) (string, string) { + var authorization, cookie string + if vals := req.Header["Authorization"]; len(vals) > 0 { + authorization = vals[0] + } + if vals := req.Header["Cookie"]; len(vals) > 0 { + cookie = vals[0] + } + return authorization, cookie +} + // extractAuthHeaders extracts auth info from request. func extractAuthHeaders(req *http.Request) http.Header { - // Marginally faster than req.Header.Get("...") - var authorization, cookie string - if vals := req.Header["Authorization"]; len(vals) > 0 { - authorization = vals[0] - } - if vals := req.Header["Cookie"]; len(vals) > 0 { - cookie = vals[0] - } + authorization, cookie := authHeaderValues(req) // Fast path: no auth headers -> no map allocation. if authorization == "" && cookie == "" { @@ // getAuthCacheKey returns cache key directly from request auth headers. func getAuthCacheKey(req *http.Request) string { - // Marginally faster than req.Header.Get("...") - var authorization, cookie string - if vals := req.Header["Authorization"]; len(vals) > 0 { - authorization = vals[0] - } - if vals := req.Header["Cookie"]; len(vals) > 0 { - cookie = vals[0] - } - + authorization, cookie := authHeaderValues(req) return authorization + ":" + cookie }The helper is inlinable, so the allocation profile stays the same. Confirm this with the existing
BenchmarkAuthCacheKey.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers.go` around lines 244 - 282, Extract the shared Authorization and Cookie lookup into one inlinable helper near extractAuthHeaders, returning both values without allocating. Update extractAuthHeaders, getAuthCacheKey, and AuthServer.getAuthUser to call this helper, preserving their existing behavior and output; confirm the existing BenchmarkAuthCacheKey remains allocation-free.managed/services/grafana/auth_server.go (2)
568-642: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueA point of order on the cache key.
getAuthCacheKeyreturns the raw credentials joined by:. The comment on line 584 states that the header comparison protects against "rare hash collisions". The key is not a hash, so two distinct credential pairs can only collide ifauthorization + ":" + cookieis ambiguous, for example("a:b", "")versus("a", "b"). The stored-header comparison does catch that case, so the behavior is correct. Update the comment so it describes the real mechanism.The raw credential is also used as the singleflight key and as the TTL-cache key. Confirm that no code path logs or exports these keys.
📝 Proposed comment fix
- // Verify auth headers for this hash to prevent serving wrong user on rare hash collisions. + // The cache key concatenates both headers, so verify the stored values + // to prevent serving the wrong user on an ambiguous concatenation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 568 - 642, Update the cache-hit comment in getAuthUser to describe validation against ambiguous raw credential keys rather than rare hash collisions, while preserving the existing authorization and cookie comparison. Inspect getAuthCacheKey and all uses of authCacheKey, including the singleflight and cache paths, to confirm the raw credentials are never logged or exported; avoid adding changes unless such exposure is found.
239-243: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoffMake it so: return the error instead of a panic.
NewAuthServerpanics whenNewCacheTTLfails. The constructor is called frommain.gowiring, where an error return gives a controlled shutdown. The failure modes ofNewCacheTTLare a nil context or a non-positive interval, so this path is unlikely today. A returned error keeps the contract safe against future changes to the constants.♻️ Proposed signature change
-func NewAuthServer(ctx context.Context, c grafanaAuthUserGetter, db *reform.DB) *AuthServer { - cache, err := cache.NewCacheTTL[cachedAuthUser](ctx, cacheItemTTL, cacheInvalidationInterval) - if err != nil { - panic(err) - } +func NewAuthServer(ctx context.Context, c grafanaAuthUserGetter, db *reform.DB) (*AuthServer, error) { + cache, err := cache.NewCacheTTL[cachedAuthUser](ctx, cacheItemTTL, cacheInvalidationInterval) + if err != nil { + return nil, fmt.Errorf("failed to create auth cache: %w", err) + }Note: this changes the callers in
main.goand in the tests and benchmarks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 239 - 243, Change NewAuthServer to return (*AuthServer, error) instead of panicking when cache.NewCacheTTL fails; return the initialization error immediately and return the server with a nil error on success. Update all callers in main.go, tests, and benchmarks to handle the constructor error explicitly.build/ansible/roles/nginx/files/conf.d/pmm.conf (1)
370-379: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRemoving the body-size limit opens a resource risk.
Line 377 sets
client_max_body_size 0for the whole/prometheusprefix, which disables the limit for every method and sub-path. The stated goal is largeremote_writepayloads. Restrict the change to the ingestion path so the rest of the prefix keeps the 10m server limit.🔒 Proposed narrowing
location ^~ /prometheus { proxy_pass http://victoriametrics; proxy_read_timeout 600; proxy_http_version 1.1; proxy_set_header Connection ""; - # Disable body size limits for large remote_write payloads - client_max_body_size 0; + # Large remote_write payloads still need a bound. + client_max_body_size 512m; client_body_buffer_size 10m; }Choose the bound from the largest expected
remote_writebatch.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/ansible/roles/nginx/files/conf.d/pmm.conf` around lines 370 - 379, Restrict the unlimited body-size configuration in the /prometheus location to the remote_write ingestion path only, rather than applying client_max_body_size 0 to the entire prefix. Preserve the existing 10m server limit for other methods and sub-paths, and set the ingestion limit according to the largest expected remote_write batch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@build/ansible/roles/nginx/files/conf.d/pmm.conf`:
- Around line 232-238: Update the `@auth_failed` location to assign valid default
values for missing $auth_code, $auth_error, and $auth_message before
constructing the response, ensuring the returned body is always valid JSON. Also
update writeResponseErrorStatus or its caller to escape or restrict message
content before writing it to the X-Auth-* header, so quotes and backslashes from
Grafana errors cannot corrupt the JSON payload.
- Around line 209-230: Update the authentication cache configuration around
proxy_cache_key to include $http_cookie alongside the existing authorization,
method, and URI components, and bypass both cache lookup and storage when
$http_authorization and $http_cookie are empty. Change the denied-response
comment to state 30 seconds, and verify that the 5-minute successful-response
TTL is acceptable relative to auth_server.go’s 60-second cacheItemTTL.
In `@managed/cmd/pmm-managed/main.go`:
- Around line 157-172: Cap the computed internal and API pool sizes in the
variables internalDbMaxOpenConns and apiDbMaxOpenConns so their combined maximum
stays within the PostgreSQL connection budget alongside Grafana. Apply the same
caps to internalDbMaxIdleConns and apiDbMaxIdleConns, preserving the existing
minimum and GOMAXPROCS-based sizing below the cap.
In `@managed/models/database.go`:
- Around line 1225-1228: Update OpenDB to apply defensive defaults when
SetupDBParams.MaxOpenConns or SetupDBParams.MaxIdleConns is zero, preserving the
previous OpenDB default via a defaultMaxOpenConns constant and using a sane
idle-connection default. Ensure zero-valued legacy callers cannot create an
unbounded pool, while retaining explicitly configured nonzero values.
In `@managed/services/agents/handler_test.go`:
- Around line 417-446: The test case around updateAgentStatus currently expects
an error for a missing agent in AGENT_STATUS_STOPPING; rename it to reflect the
successful outcome and replace the error assertions with require.NoError(t,
err), while preserving the existing mock setup and expectation verification.
- Around line 332-360: Validate StateChangedRequest.listen_port before invoking
checkPortChanged or updateAgentStatus, rejecting any value above math.MaxUint16
rather than narrowing it to uint16. Add coverage alongside the existing
wrapped-port test to verify an out-of-range port is rejected and no agent update
is performed.
In `@managed/services/agents/registry.go`:
- Around line 378-391: The cache operations are not atomic, allowing duplicate
registration and repeated agent closure. In managed/services/agents/registry.go
lines 378-391, add and use Cache[V].LoadAndDelete in Registry.unregister so only
one concurrent Kick receives the agent; in lines 268-295, replace the separate
existence check and Set with an atomic store-if-absent operation; in
managed/services/agents/registry_test.go lines 120-165, add concurrent Kick
coverage for one agent and run the package with -race.
In `@managed/services/agents/state.go`:
- Around line 216-232: Remove the in-flight u.dbGroup.Forget("settings") call
from the singleflight callback and remove the other corresponding Forget call in
the surrounding settings-fetch flow; rely on singleflight.Group.Do cleanup
without moving either call after Do. Update the related error text from
“fetching settings” to “fetching node info” and correct “preasure” to
“pressure”.
In `@managed/services/grafana/auth_server_fuzz.go`:
- Line 48: Update the gofuzz harness around clientStub, NewAuthServer, and
processRequest to match the current AuthServer API signatures. In the fuzz
entrypoint, call extractOriginalRequest before processRequest and handle both
values it returns, then pass the resulting request data using the updated
processRequest arguments.
In `@managed/services/qan/client.go`:
- Around line 182-193: Update the service lookup in the query flow to pass the
context-bound querier q to collectServices instead of c.db.Querier, preserving
the client cancellation and stream deadline through the database query.
---
Outside diff comments:
In `@managed/services/agents/registry.go`:
- Around line 268-295: The duplicate-connection check and insertion in the agent
registration flow are not atomic, allowing concurrent connections with the same
ID to overwrite each other. Update the logic around the registry method
containing r.agentsCache.Get and Set to use the cache’s atomic store-if-absent
operation, preserving the existing AlreadyExists response and ping/kick handling
for an already registered agent.
---
Minor comments:
In `@build/ansible/roles/nginx/files/conf.d/pmm.conf`:
- Around line 68-89: Correct the cache sizing comments in the nginx
configuration: update the STATIC keys_zone description to state 1 MB, matching
keys_zone=STATIC:1m, and update the AUTH_CACHE disk-footprint description to
state 128 MB, matching max_size=128m. Leave the directives unchanged.
In `@managed/cmd/pmm-managed/main.go`:
- Around line 1086-1090: Update the API connection limits used by
agents.NewStateUpdater and pmmAgentsConnectionsLimiter so their combined caps
never exceed apiDbMaxOpenConns, while treating sequential state-update queries
as non-concurrent. Change the state-updater comment from “reserve” to “cap” and
use either a shared budget or complementary percentages totaling at most 100%.
In `@managed/services/agents/registry.go`:
- Around line 252-255: Update the authentication flow around authenticate and
the PMM agent version parsing so md.Version is parsed only once and the parsed
version is reused when constructing the node. Propagate the parsed version
alongside the authenticated node, or convert this failure to a gRPC status.Error
with codes.InvalidArgument, ensuring malformed versions consistently return that
status instead of codes.Unknown.
In `@managed/services/grafana/auth_server.go`:
- Around line 463-466: Update the no-roles branch in the surrounding service
method to replace the package-level logrus.Errorf call with the component logger
entry s.l, preserving the existing message and error return while retaining the
entry’s structured component field.
In `@managed/services/grafana/helpers_test.go`:
- Around line 176-199: Update the single-element
`"/v1/server/AWSInstanceCheck/..%2f..%2finventory/Services/List'"` entry in
`TestNextPrefix` so it contains the complete expected `nextPrefix` chain,
confirming each value against the implementation; alternatively remove the entry
if no chain is intended. Ensure every test case produces at least one assertion.
In `@managed/services/realtimeanalytics/service.go`:
- Around line 539-544: Update the agent validation warning in the FindAgentByID
error path to use l.WithError(err).Warn with a descriptive message, rather than
interpolating err via Warnf. Preserve the existing disconnect behavior and
InvalidArgument response.
In `@managed/services/victoriametrics/victoriametrics.go`:
- Line 485: Move the HA-mode comment currently trailing the closing brace onto
its own line immediately before the skipExternalExporter assignment it
describes, leaving the error-check closing brace unannotated.
In `@utils/cache/cache_test.go`:
- Around line 21-143: Replace direct t.Fatal and t.Fatalf assertions in
utils/cache/cache_test.go lines 21-143 and utils/cache/cache_ttl_test.go lines
27-205 with testify/assert and testify/require helpers. Use require for setup or
precondition checks and assert for value comparisons, importing the helpers
consistently while preserving each test’s existing expectations.
In `@utils/cache/cache_ttl_bench_test.go`:
- Around line 15-16: Remove the AGPL distribution notice from the Apache-2.0
Percona headers in utils/cache/cache_ttl_bench_test.go lines 15-16 and
utils/cache/cache_ttl_test.go lines 15-16, leaving only the standard Apache-2.0
Percona license header for both Go test files.
In `@utils/rateLimiter/concurrencyLimiter_bench_test.go`:
- Around line 22-55: Replace direct Fatal-based assertions in
BenchmarkConcurrencyLimiter_TryAcquireRelease and
BenchmarkConcurrencyLimiter_TryAcquireWhenExhausted in
utils/rateLimiter/concurrencyLimiter_bench_test.go, and the corresponding
assertions in utils/rateLimiter/concurrencyLimiter_test.go lines 23-110, with
testify require or assert calls; add or reuse the appropriate testify import
while preserving each test’s existing expectations.
In `@utils/rateLimiter/concurrencyLimiter.go`:
- Around line 39-61: Update utils/rateLimiter/concurrencyLimiter.go lines 39-61:
have ConcurrencyLimiter retain maxSlots when NewConcurrencyLimiter initializes
it, and bound Release so availableSlots never exceeds that configured maximum.
Update utils/rateLimiter/concurrencyLimiter_test.go lines 71-83 by replacing the
release-before-acquire expectation with an assertion that capacity remains
capped at maxSlots.
---
Nitpick comments:
In `@build/ansible/roles/nginx/files/conf.d/pmm.conf`:
- Around line 370-379: Restrict the unlimited body-size configuration in the
/prometheus location to the remote_write ingestion path only, rather than
applying client_max_body_size 0 to the entire prefix. Preserve the existing 10m
server limit for other methods and sub-paths, and set the ingestion limit
according to the largest expected remote_write batch.
In `@managed/cmd/pmm-managed/main.go`:
- Line 1211: Update the settings lookup around models.GetSettings to use the
reform database handle with the current context, matching the existing
deps.db.WithContext(ctx) pattern instead of passing raw sqlInternalDB. Preserve
the existing error handling and settings flow.
- Around line 906-971: Extract the duplicated database configuration into a
newDBParams helper accepting maxIdle and maxOpen, and use it for both
setupInternalDBParams and setupAPIDBParams while preserving their pool-specific
limits. Differentiate the failure messages so the internal pool uses its own
context and the API pool reports “Failed to connect to the API database pool”.
Correct the “serviceses” typo and remove the stray tab in the internal DB
comment.
In `@managed/services/agents/registry_test.go`:
- Around line 120-165: Add a concurrency test alongside
TestRegistryKickRemovesAgentAndClosesKickChannel that launches multiple
goroutines calling Registry.Kick for the same agent, waits for all calls to
finish, and verifies the agent is removed without panic or race under -race. Use
a synchronization mechanism compatible with the repository’s conventions and
ensure every goroutine has a bounded, context- or errgroup-tied completion path.
- Around line 167-191: Add haService and victoriaMetricsParams to the configured
mockery interfaces in .mockery.yaml, generate their mocks alongside
mock_limiter_test.go, and update the affected tests to use the generated mocks
instead of fakeHAService and fakeVictoriaMetricsParams. Preserve the existing
interface behavior and use mock expectations where applicable.
In `@managed/services/agents/state_test.go`:
- Around line 89-127: Extend state updater tests beyond
TestUpdateAgentsStateQueuesUpdatesForAllConnectedAgents to cover
runStateChangeHandler batching and exits on kickChan/context cancellation,
stateUpdateRateLimiter rejection including errStateUpdateLimitExceeded, backoff
retry and reset behavior, and sendSetStateRequest singleflight deduplication.
Use concurrent updates with maxConcurrentUpdates set to 1 to exercise limiter
rejection, and retain SQL expectations for the non-transactional
UpdateAgentsState path.
In `@managed/services/agents/state.go`:
- Around line 288-330: Refactor the state-building flow around the agent
iteration to collect all service IDs and RDS node IDs first, resolve them with
batched filtered queries, and index the results by ID. Update the
AzureDatabaseExporterType and RDSExporterType branches to reuse those maps
instead of calling models.FindServiceByID or models.FindNodeByID per row, while
preserving existing lookup errors and configuration behavior.
In `@managed/services/grafana/auth_server_bench_test.go`:
- Around line 118-131: Move b.ReportAllocs() from the parent benchmark into the
b.Run subtest callback within the benchmark table loop, so allocation reporting
applies to each subtest.
In `@managed/services/grafana/auth_server_test.go`:
- Around line 203-220: Remove the unused c and ac mock setup, including their
expectation cleanup and related isEnabled expectation, from the “enabled LBAC -
lbacPrefixes” test; keep setupLBACServer(t) as the sole server initialization
before exercising needAddLBACFilters.
- Around line 511-527: Remove the commented-out cache assertions in the
anonymous-user test and the matching cases around the other referenced tests, or
restore them using the cacheSize(s) helper. Verify the expected cache count
first, since authenticateUser caches successful getAuthUser responses even for
anonymous users.
In `@managed/services/grafana/auth_server.go`:
- Around line 568-642: Update the cache-hit comment in getAuthUser to describe
validation against ambiguous raw credential keys rather than rare hash
collisions, while preserving the existing authorization and cookie comparison.
Inspect getAuthCacheKey and all uses of authCacheKey, including the singleflight
and cache paths, to confirm the raw credentials are never logged or exported;
avoid adding changes unless such exposure is found.
- Around line 239-243: Change NewAuthServer to return (*AuthServer, error)
instead of panicking when cache.NewCacheTTL fails; return the initialization
error immediately and return the server with a nil error on success. Update all
callers in main.go, tests, and benchmarks to handle the constructor error
explicitly.
In `@managed/services/grafana/helpers_bench_test.go`:
- Around line 27-47: Remove the commented-out cleanPath verification block in
BenchmarkCleanPath, including the commented require.NoError and require.Equal
lines, while leaving the active benchmark loop and its validations unchanged.
- Around line 97-101: Update BenchmarkResolveRule to stop mutating the global
standard logger via logrus.SetOutput; create a local logrus.Logger configured to
discard output, then build the log entry from that local logger while preserving
the benchmark’s existing behavior.
In `@managed/services/grafana/helpers_test.go`:
- Around line 264-270: Rename the local test-case slice `tests` in
`TestCleanPath` to avoid shadowing the imported `tests` package, and update the
associated loop at line 321 to use the new name consistently.
In `@managed/services/grafana/helpers.go`:
- Around line 244-282: Extract the shared Authorization and Cookie lookup into
one inlinable helper near extractAuthHeaders, returning both values without
allocating. Update extractAuthHeaders, getAuthCacheKey, and
AuthServer.getAuthUser to call this helper, preserving their existing behavior
and output; confirm the existing BenchmarkAuthCacheKey remains allocation-free.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: b5e77f84-9373-41c6-bdc4-a5f1680facd3
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.summanaged/cmd/pmm-managed/packages.dotis excluded by!**/*.dot
📒 Files selected for processing (46)
.golangci.yml.mockery.yamlbuild/ansible/roles/nginx/files/conf.d/pmm.confbuild/ansible/roles/nginx/files/nginx.confbuild/docker/server/entrypoint.shdashboards/dashboards/PMM Health/PMM_Health.jsondocker-compose.dev.ymlgo.modmanaged/cmd/pmm-managed/main.gomanaged/models/database.gomanaged/services/agents/deps.gomanaged/services/agents/handler.gomanaged/services/agents/handler_test.gomanaged/services/agents/mock_limiter_test.gomanaged/services/agents/registry.gomanaged/services/agents/registry_test.gomanaged/services/agents/state.gomanaged/services/agents/state_test.gomanaged/services/grafana/access_control_cache.gomanaged/services/grafana/auth_server.gomanaged/services/grafana/auth_server_bench_test.gomanaged/services/grafana/auth_server_fuzz.gomanaged/services/grafana/auth_server_test.gomanaged/services/grafana/deps.gomanaged/services/grafana/helpers.gomanaged/services/grafana/helpers_bench_test.gomanaged/services/grafana/helpers_test.gomanaged/services/grafana/mock_access_control_test.gomanaged/services/grafana/mock_grafana_auth_user_getter_test.gomanaged/services/qan/client.gomanaged/services/realtimeanalytics/deps.gomanaged/services/realtimeanalytics/mock_limiter_test.gomanaged/services/realtimeanalytics/service.gomanaged/services/realtimeanalytics/service_test.gomanaged/services/victoriametrics/victoriametrics.gomanaged/utils/interceptors/interceptors.goutils/cache/cache.goutils/cache/cache_bench_test.goutils/cache/cache_test.goutils/cache/cache_ttl.goutils/cache/cache_ttl_bench_test.goutils/cache/cache_ttl_test.goutils/cache/common.goutils/rateLimiter/concurrencyLimiter.goutils/rateLimiter/concurrencyLimiter_bench_test.goutils/rateLimiter/concurrencyLimiter_test.go
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
percona/pmm-qa(manual)percona/pmm(manual)
|
@coderabbitai full review |
✅ Action performedFull review finished. |
✅ Action performedFull review finished. |
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (14)
managed/services/agents/registry.go (1)
92-93: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the field detail to a separate comment line.
Line 93 has an inline comment. Place
id -> infoin the preceding documentation comment.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry.go` around lines 92 - 93, Move the “id -> info” detail from the inline comment on agentsCache to the preceding documentation comment, leaving the field declaration without an inline comment.Source: Coding guidelines
managed/services/grafana/helpers_bench_test.go (2)
31-37: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the commented-out verification block.
Lines 33 to 35 hold commented-out
requirecalls. The file does not importtestify, so the lines cannot be restored as written. The loop body at lines 39 to 45 already performs the same check withb.Fatalf.♻️ Proposed cleanup
b.ReportAllocs() - // cleanedPath, err := cleanPath(unescapedURI) - // require.NoError(b, err) - // require.Equal(b, expectedCleanPath, cleanedPath) - b.ResetTimer()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_bench_test.go` around lines 31 - 37, Remove the commented-out cleanPath verification block between b.ReportAllocs() and b.ResetTimer() in the benchmark, leaving the existing b.Fatalf-based validation in the loop unchanged.
97-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDo not mutate the global
logruslogger from a benchmark.Line 100 calls
logrus.SetOutput(io.Discard)on the standard logger and never restores it.go testruns tests and benchmarks in one process, so this silences logging for every other test in the package. The effect depends on execution order, which makes failures hard to reproduce.Use a logger instance scoped to this benchmark instead.
♻️ Proposed change
func BenchmarkResolveRule(b *testing.B) { - b.ReportAllocs() - - logrus.SetOutput(io.Discard) - l := logrus.NewEntry(logrus.StandardLogger()) + logger := logrus.New() + logger.SetOutput(io.Discard) + l := logrus.NewEntry(logger) + for _, tc := range []struct { name string method string path string }{ @@ b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() for b.Loop() { _, _ = resolveRule(tc.method, tc.path, l) } })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_bench_test.go` around lines 97 - 101, Remove the global logrus.StandardLogger mutation in BenchmarkResolveRule and configure a benchmark-scoped logger instance instead. Update the logger setup used to create l so its output is discarded without affecting other tests or benchmarks.build/ansible/roles/nginx/files/conf.d/pmm.conf (3)
247-307: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCorrect the misleading comment on the assets location.
Line 267 states that the assets are dynamic. Lines 274 and 275 then set
expires 30dandCache-Control "public, max-age=2592000, immutable". Those directives describe immutable static assets, which is the opposite of dynamic.The likely intent is that the asset filenames are content-hashed, so each file never changes. State that instead, because a future reader may otherwise remove the caching headers as inconsistent.
📝 Proposed comment correction
- # All PMM UI assets are dynamic - bypass authentication and cache on browser side. + # PMM UI asset filenames are content-hashed, so each file is immutable. + # Bypass authentication and allow long-lived browser caching. location ^~ /pmm-ui/assets/ {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/ansible/roles/nginx/files/conf.d/pmm.conf` around lines 247 - 307, Update the comment above the /pmm-ui/assets/ location to state that the assets use content-hashed filenames and are immutable static files, consistent with the 30-day expiration and immutable Cache-Control directives. Leave the caching configuration unchanged.
326-352: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
expiresandadd_header Cache-Controltogether emit twoCache-Controlheader lines.Line 335 sets
expires 30d, which makes NGINX emitCache-Control: max-age=2592000. Line 336 then adds a secondCache-Control: public, no-transformline. The response therefore carries two separateCache-Controlheaders, and the directives are split across them. Intermediate caches vary in how they merge such headers.Set one complete value instead.
Line 343 also omits the
alwaysflag onadd_header X-Cache-Status, so the debug header disappears on error responses. Line 277 usesalwaysfor the equivalent header. Align the two for consistent diagnostics.♻️ Proposed change
- # Add caching headers to further reduce container load - expires 30d; - add_header Cache-Control "public, no-transform"; + # Add caching headers to further reduce container load + expires 30d; + add_header Cache-Control "public, max-age=2592000, no-transform" always; @@ - add_header X-Cache-Status $upstream_cache_status; + add_header X-Cache-Status $upstream_cache_status always;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/ansible/roles/nginx/files/conf.d/pmm.conf` around lines 326 - 352, Update the Grafana static-assets location to emit a single complete Cache-Control header instead of combining expires with add_header Cache-Control, preserving the intended 30-day public caching and no-transform directives. Also update the X-Cache-Status add_header in this location to use the always flag, matching the equivalent configuration near line 277.
87-89: 🚀 Performance & Scalability | 🔵 TrivialConsider the key capacity of
AUTH_CACHEagainst the agent fleet size.
AUTH_CACHE:1mholds roughly 8,000 keys, per the estimate at line 78. The cache key at line 219 is"$http_authorization|$request_method|$request_uri", so each agent credential consumes one entry for the write endpoint.For a deployment with more distinct agent credentials than the zone capacity, NGINX evicts entries under LRU pressure. The authentication cache then misses often, and the load returns to pmm-managed. That result is the opposite of the goal stated at lines 399 to 401.
Add a metric or alert on
$upstream_cache_statusfor this location so eviction pressure is visible, and document the fleet size at which the zone requires enlargement.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@build/ansible/roles/nginx/files/conf.d/pmm.conf` around lines 87 - 89, Add monitoring for $upstream_cache_status in the auth-cache location, exposing or alerting on cache misses/evictions so LRU pressure is visible. Update the nearby AUTH_CACHE documentation to state the approximate credential capacity and the agent-fleet size at which the 1m zone must be enlarged.managed/services/grafana/helpers_test.go (1)
176-199: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOne table row asserts nothing.
The loop at line 190 iterates
paths[:len(paths)-1], which treats the final element as the expected result of the previous one. The row at line 186 holds a single element, so the slice is empty. That row runs no assertion and does not reachtests.AddToFuzzCorpus.Either add the expected prefix chain for that path or delete the row, so the coverage matches what the table appears to declare.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers_test.go` around lines 176 - 199, Update the single-element test row beginning with "/v1/server/AWSInstanceCheck/.." in TestNextPrefix so it declares the expected nextPrefix result, or remove the row if no assertion is intended. Ensure the row produces at least one assertion and calls tests.AddToFuzzCorpus for the path.managed/services/grafana/auth_server_bench_test.go (1)
103-119: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
b.ReportAllocson the parent benchmark does not apply to the sub-benchmarks.Line 118 calls
b.ReportAllocs()on the outer*testing.B. Theb.Runcalls that follow create separate*testing.Bvalues, and each must enable allocation reporting itself. As written, the per-route benchmarks report no allocation counts.This PR targets heap-allocation reduction on the authentication path, so the allocation numbers are the primary signal here.
♻️ Proposed change
grafanaMock.On("getAuthUser", mock.Anything, mock.Anything, mock.Anything). Return(authUser{role: admin, userID: 1001}, nil) - b.ReportAllocs() - for _, tc := range []struct { name string method string path string }{ @@ b.Run(tc.name, func(b *testing.B) { + b.ReportAllocs() tokenSeq := 0 for b.Loop() {The same placement occurs in
managed/services/grafana/helpers_bench_test.go.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_bench_test.go` around lines 103 - 119, Move allocation reporting from the parent benchmarks to each sub-benchmark created by b.Run in BenchmarkAuthServerServeHTTP and the corresponding benchmark in helpers_bench_test.go. Call ReportAllocs on each sub-benchmark’s *testing.B so every per-route benchmark reports allocation counts.managed/services/grafana/auth_server.go (3)
499-512: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider a lower log level for expected client authentication and authorization failures.
Lines 501 and 509 log at
Errorlevel. Both conditions are routine client outcomes: an expired token produces the first, and an insufficient role produces the second. This handler runs on every authenticated request, so invalid credentials from one misconfigured agent can fill the log withErrorrecords and mask genuine server faults.Reserve
Errorfor server-side faults, such aserrStaticAuthErrorInternalError. UseWarnorDebugfor denials.♻️ Proposed change to the log levels
user, err := s.authenticateUser(req, l) //nolint:contextcheck if err != nil { - l.WithError(err).Error("Failed to authenticate user.") + l.WithError(err).Warn("Failed to authenticate user.") var zero authResult return zero, err } l = l.WithField("role", user.role.String()) err = authorizeUser(minRole, user, l) if err != nil { - l.WithError(err).Error("Failed to authorize user.") + l.WithError(err).Warn("Failed to authorize user.") var zero authResult return zero, err }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 499 - 512, Lower the log levels for the expected failure paths in the authentication handler: change the logging for authenticateUser failures and authorizeUser denials from Error to Warn or Debug, while preserving Error for server-side faults such as errStaticAuthErrorInternalError.
463-466: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the component logger instead of the package-level
logruslogger.Replacing the panic with an error return is a solid improvement, Number One. One detail remains: line 464 calls
logrus.Errorfon the standard logger. That call drops thecomponentfield carried bys.l, so these records lose correlation with the rest of the auth component.The coding guidelines require structured logging through a
*logrus.Entry.♻️ Proposed change to use the structured entry
if len(roles) == 0 { - logrus.Errorf("User %d has no roles", userID) + s.l.WithField("user_id", userID).Error("User has no roles.") return nil, fmt.Errorf("user %d has no roles", userID) }As per coding guidelines: "Use structured
logruslogging with*logrus.Entry, such ass.l.WithField(...).Error(...)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 463 - 466, Update the no-roles branch in the relevant auth server method to replace the package-level logrus.Errorf call with the component logger entry s.l, preserving the existing message and error return while retaining structured component fields.Source: Coding guidelines
580-627: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winUse an unambiguous cache key separator, and re-verify credentials on the singleflight result.
Engage, but with one course correction. Two related weaknesses share a root cause: the key format.
getAuthCacheKeyinmanaged/services/grafana/helpers.goreturnsauthorization + ":" + cookie. The:character occurs inside both header values, so distinct credential pairs can produce one identical key. For example,Authorization: "A:B"withCookie: "C"andAuthorization: "A"withCookie: "B:C"both yieldA:B:C.The cache path handles this. Lines 585 and 600 compare the stored
authorizationandcookieagainst the request values, so a colliding entry falls through to the cold path.The singleflight path does not. Line 596 uses the same ambiguous string as the singleflight key. Line 611 authenticates with
extractAuthHeaders(req)from the closure of whichever caller became the leader. A waiter that collided on the key receives the leader'sauthUserat line 634 with no comparison against its own headers. The waiter then proceeds with another identity and role.Exploitation requires prior knowledge of the target credentials, so this is not an authentication bypass. It is still a correctness defect on the authorization path, and one delimiter change removes the whole class.
🔒 Proposed fix: length-prefixed key plus a result re-check
In
managed/services/grafana/helpers.go:// getAuthCacheKey returns cache key directly from request auth headers. func getAuthCacheKey(req *http.Request) string { // Marginally faster than req.Header.Get("...") var authorization, cookie string if vals := req.Header["Authorization"]; len(vals) > 0 { authorization = vals[0] } if vals := req.Header["Cookie"]; len(vals) > 0 { cookie = vals[0] } - return authorization + ":" + cookie + // Length-prefix the first field so the boundary is unambiguous. + // "A:B" + "C" and "A" + "B:C" must not produce one key. + var b strings.Builder + b.Grow(len(authorization) + len(cookie) + 12) //nolint:mnd + b.WriteString(strconv.Itoa(len(authorization))) + b.WriteByte(':') + b.WriteString(authorization) + b.WriteString(cookie) + return b.String() }In
managed/services/grafana/auth_server.go, re-check the deduplicated result:user, ok := res.(authUser) if !ok { l.WithField("type", fmt.Sprintf("%T", res)).Error("Unexpected Grafana user result type.") var zero authUser return zero, errStaticAuthErrorInternalError } + + // The singleflight leader authenticated with its own headers. Confirm the + // cached entry it stored matches this request's credentials before use. + if cached, found := s.cache.Load(authCacheKey); found { + if cached.authorization != authorization || cached.cookie != cookie { + l.Error("Auth cache key collision detected; rejecting deduplicated result.") + var zero authUser + return zero, errStaticAuthErrorInternalError + } + } return user, nil🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 580 - 627, Update getAuthCacheKey to use an unambiguous, length-prefixed encoding of authorization and cookie instead of concatenating them with “:”. In the authUserGroup.Do result handling, re-validate the returned user against the current request’s authorization and cookie before returning it; if they do not match, do not accept the deduplicated result and ensure the request is authenticated with its own credentials.managed/services/grafana/helpers.go (1)
258-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract the duplicated header reads into one helper.
The same block that reads
req.Header["Authorization"][0]andreq.Header["Cookie"][0], including the identical comment, appears three times:
extractAuthHeaders, lines 261 to 267.getAuthCacheKey, lines 287 to 293.AuthServer.getAuthUserinmanaged/services/grafana/auth_server.go, lines 572 to 578.
getAuthUsercalls both helpers and then repeats the reads a third time for the collision check. One small accessor removes all three copies and keeps the zero-allocation property.♻️ Proposed helper
+// authCredentials returns the raw Authorization and Cookie header values. +// Direct map access is marginally faster than req.Header.Get. +func authCredentials(req *http.Request) (string, string) { + var authorization, cookie string + if vals := req.Header["Authorization"]; len(vals) > 0 { + authorization = vals[0] + } + if vals := req.Header["Cookie"]; len(vals) > 0 { + cookie = vals[0] + } + return authorization, cookie +} + // extractAuthHeaders extracts auth info from request. func extractAuthHeaders(req *http.Request) http.Header { - // Marginally faster than req.Header.Get("...") - var authorization, cookie string - if vals := req.Header["Authorization"]; len(vals) > 0 { - authorization = vals[0] - } - if vals := req.Header["Cookie"]; len(vals) > 0 { - cookie = vals[0] - } + authorization, cookie := authCredentials(req)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/helpers.go` around lines 258 - 296, Extract the repeated Authorization and Cookie header reads into a shared accessor, then update extractAuthHeaders, getAuthCacheKey, and AuthServer.getAuthUser—including its collision check—to reuse it. Preserve the current first-value selection, empty-header behavior, and zero-allocation fast path.managed/services/grafana/auth_server_test.go (2)
511-526: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRestore or delete the commented-out cache assertions.
Four subtests end with a commented-out assertion that uses the old map-based cache field:
- Line 525 in
TestAuthServerAuthenticateUser.- Line 801 in
TestAuthServerProcessRequest, subtest "access forbidden for anonymous user".- Line 818 in the same test, subtest "access granted for anonymous user".
- Line 836 in the same test, subtest "access granted for anonymous user with LBAC enabled".
Each states that the cache must stay empty for an anonymous user. That behavior is unverified. The cache is now populated by
getAuthUserfor any successful Grafana lookup, including one that returnsuserID: 0, so the stated expectation may no longer hold.This file already provides the
cacheSizehelper. Either assert with it or delete the comments.♻️ Proposed change for line 525
got, err := s.authenticateUser(req, l) require.NoError(t, err) assert.Equal(t, userInfo, got) - // assert.True(t, len(s.cache) == 0, "cache should be empty on anonymous user") + assert.Equal(t, int64(1), cacheSize(s), "anonymous lookups are cached like any other")Confirm the intended caching behavior for anonymous identities, then apply the matching assertion at all four sites.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_test.go` around lines 511 - 526, Confirm the intended caching behavior for anonymous identities, then update all four commented-out cache checks in TestAuthServerAuthenticateUser and TestAuthServerProcessRequest to use the existing cacheSize helper with the matching expected value, or remove the assertions if anonymous lookups are intentionally cached; do not leave the stale commented assertions.
203-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the unused mocks in this subtest.
Lines 205 to 211 create
candac, registerac.On("isEnabled"), and add a cleanup that asserts both. Line 213 then callssetupLBACServer(t), which builds its own mocks and assigns them to the server.candacare never attached tos, so the assertions verify nothing. The.Maybe()qualifier keepsAssertExpectationspassing, which hides the fact that the mocks are inert.♻️ Proposed cleanup
t.Run("enabled LBAC - lbacPrefixes", func(t *testing.T) { t.Parallel() - c := newMockGrafanaAuthUserGetter(t) - ac := newMockAccessControl(t) - ac.On("isEnabled").Return(true).Maybe() - t.Cleanup(func() { - c.AssertExpectations(t) - ac.AssertExpectations(t) - }) s, _, _ := setupLBACServer(t)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_test.go` around lines 203 - 219, Remove the unused c and ac mock setup, expectation registration, and cleanup from the “enabled LBAC - lbacPrefixes” subtest; rely on setupLBACServer(t) to configure the mocks used by s, while preserving the existing prefix assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@build/ansible/roles/nginx/files/conf.d/pmm.conf`:
- Around line 381-397: Update the proxy header configuration in
build/ansible/roles/nginx/files/conf.d/pmm.conf: add X-Proxy-Filter and
X-Forwarded-For to the /prometheus/api/v1 and /victoriametrics/ locations, which
declare proxy_set_header Connection. At lines 115-118, eliminate reliance on
server-level inheritance by moving the shared headers into an include used by
every proxying location or repeating them in each such location. Add an
integration check verifying restrictive filters limit the series returned by
/prometheus/api/v1/query.
- Around line 68-89: Correct the comments for the STATIC and AUTH_CACHE
proxy_cache_path directives: change the STATIC keys_zone allocation description
from 10 MB to 1 MB, and change the AUTH_CACHE maximum disk footprint description
from 10M to 128M. Leave the nginx directives unchanged.
- Around line 115-118: Update the NGINX configuration so every intended proxied
location explicitly applies the X-Forwarded-For header, rather than relying on
the overridden server-level directive. Add the setting to each of the 16
location blocks or reuse a shared snippet, and separately remove or adjust the
vmproxy handling so VictoriaMetrics receives the header when required.
In `@managed/cmd/pmm-managed/main.go`:
- Around line 177-182: The API database limiters currently allocate independent
budgets that can exceed apiDbMaxOpenConns; update pmmAgentsConnectionsLimiter
and the state-update limiter around their existing declarations/usages to share
one limiter or partition their capacities so the combined maximum never exceeds
apiDbMaxOpenConns, while preserving both paths’ concurrency control.
In `@managed/services/agents/registry.go`:
- Around line 234-237: Wrap the raw errors at all three affected sites with
descriptive %w context: in managed/services/agents/registry.go lines 234-237,
update the metadata receive error in the agent connection flow; in lines
263-265, update the server metadata send error; and in
managed/services/victoriametrics/victoriametrics.go lines 482-489, wrap both the
settings lookup and scrape-config generation errors. Preserve error unwrapping
by using %w and describe the failed operation in each message.
In `@managed/services/grafana/auth_server.go`:
- Around line 538-550: Update authenticateUser and the local-agent trust
mechanism so requests proxied by NGINX cannot satisfy isLocalAgentConnection and
receive static admin credentials; either separate the trusted local-agent
listener from the auth endpoint or validate a trusted NGINX-provided original
client address before using staticAuthUsers, while preserving authentication for
legitimate local-agent connections.
- Around line 385-392: Update AuthServer.addLBACFilters so userID <= 0 returns
ErrInvalidUserID instead of an empty filter and nil error, ensuring ServeHTTP
rejects anonymous requests on LBAC-protected paths rather than proxying them
without X-Proxy-Filter.
In `@managed/services/grafana/helpers.go`:
- Around line 71-91: Fix jsonStringValueEscaper used by escapeJSONStringValue so
each single backslash becomes two backslashes and each quote becomes a
backslash-plus-quote JSON escape, preserving valid JSON interpolation. Update
TestWriteResponseErrorStatus to verify auth error headers containing both
backslashes and quotes are escaped correctly.
In `@utils/cache/cache_test.go`:
- Around line 17-20: Update the tests in cache_test.go to import testify's
assert and require packages, replacing manual condition checks and
t.Fatal/t.Fatalf calls with the appropriate assertion helpers while preserving
each test's existing expectations and failure behavior.
In `@utils/cache/cache_ttl_bench_test.go`:
- Around line 15-16: Remove the conflicting AGPL notice from
utils/cache/cache_ttl_bench_test.go lines 15-16 and
utils/cache/cache_ttl_test.go lines 15-16, preserving their existing Apache-2.0
license declarations.
In `@utils/rateLimiter/concurrencyLimiter_test.go`:
- Around line 17-110: Update the tests in this file to use testify/require
assertions: replace direct t.Fatal checks with require.True or require.False for
boolean expectations and require.Equal for the final success-count comparison in
TestConcurrencyLimiter_TryAcquireConcurrentCallersNeverExceedsLimit. Add the
required testify/require import while preserving the existing test behavior and
messages where applicable.
In `@utils/rateLimiter/concurrencyLimiter.go`:
- Around line 59-61: The ConcurrencyLimiter must prevent unmatched Release calls
from exceeding its configured capacity. In
utils/rateLimiter/concurrencyLimiter.go at lines 59-61, retain the configured
maximum in ConcurrencyLimiter and update Release to cap availableSlots at that
maximum; in utils/rateLimiter/concurrencyLimiter_test.go at lines 71-83, replace
the unmatched-release success expectation with a test asserting the
acquire-release invariant.
---
Nitpick comments:
In `@build/ansible/roles/nginx/files/conf.d/pmm.conf`:
- Around line 247-307: Update the comment above the /pmm-ui/assets/ location to
state that the assets use content-hashed filenames and are immutable static
files, consistent with the 30-day expiration and immutable Cache-Control
directives. Leave the caching configuration unchanged.
- Around line 326-352: Update the Grafana static-assets location to emit a
single complete Cache-Control header instead of combining expires with
add_header Cache-Control, preserving the intended 30-day public caching and
no-transform directives. Also update the X-Cache-Status add_header in this
location to use the always flag, matching the equivalent configuration near line
277.
- Around line 87-89: Add monitoring for $upstream_cache_status in the auth-cache
location, exposing or alerting on cache misses/evictions so LRU pressure is
visible. Update the nearby AUTH_CACHE documentation to state the approximate
credential capacity and the agent-fleet size at which the 1m zone must be
enlarged.
In `@managed/services/agents/registry.go`:
- Around line 92-93: Move the “id -> info” detail from the inline comment on
agentsCache to the preceding documentation comment, leaving the field
declaration without an inline comment.
In `@managed/services/grafana/auth_server_bench_test.go`:
- Around line 103-119: Move allocation reporting from the parent benchmarks to
each sub-benchmark created by b.Run in BenchmarkAuthServerServeHTTP and the
corresponding benchmark in helpers_bench_test.go. Call ReportAllocs on each
sub-benchmark’s *testing.B so every per-route benchmark reports allocation
counts.
In `@managed/services/grafana/auth_server_test.go`:
- Around line 511-526: Confirm the intended caching behavior for anonymous
identities, then update all four commented-out cache checks in
TestAuthServerAuthenticateUser and TestAuthServerProcessRequest to use the
existing cacheSize helper with the matching expected value, or remove the
assertions if anonymous lookups are intentionally cached; do not leave the stale
commented assertions.
- Around line 203-219: Remove the unused c and ac mock setup, expectation
registration, and cleanup from the “enabled LBAC - lbacPrefixes” subtest; rely
on setupLBACServer(t) to configure the mocks used by s, while preserving the
existing prefix assertions.
In `@managed/services/grafana/auth_server.go`:
- Around line 499-512: Lower the log levels for the expected failure paths in
the authentication handler: change the logging for authenticateUser failures and
authorizeUser denials from Error to Warn or Debug, while preserving Error for
server-side faults such as errStaticAuthErrorInternalError.
- Around line 463-466: Update the no-roles branch in the relevant auth server
method to replace the package-level logrus.Errorf call with the component logger
entry s.l, preserving the existing message and error return while retaining
structured component fields.
- Around line 580-627: Update getAuthCacheKey to use an unambiguous,
length-prefixed encoding of authorization and cookie instead of concatenating
them with “:”. In the authUserGroup.Do result handling, re-validate the returned
user against the current request’s authorization and cookie before returning it;
if they do not match, do not accept the deduplicated result and ensure the
request is authenticated with its own credentials.
In `@managed/services/grafana/helpers_bench_test.go`:
- Around line 31-37: Remove the commented-out cleanPath verification block
between b.ReportAllocs() and b.ResetTimer() in the benchmark, leaving the
existing b.Fatalf-based validation in the loop unchanged.
- Around line 97-101: Remove the global logrus.StandardLogger mutation in
BenchmarkResolveRule and configure a benchmark-scoped logger instance instead.
Update the logger setup used to create l so its output is discarded without
affecting other tests or benchmarks.
In `@managed/services/grafana/helpers_test.go`:
- Around line 176-199: Update the single-element test row beginning with
"/v1/server/AWSInstanceCheck/.." in TestNextPrefix so it declares the expected
nextPrefix result, or remove the row if no assertion is intended. Ensure the row
produces at least one assertion and calls tests.AddToFuzzCorpus for the path.
In `@managed/services/grafana/helpers.go`:
- Around line 258-296: Extract the repeated Authorization and Cookie header
reads into a shared accessor, then update extractAuthHeaders, getAuthCacheKey,
and AuthServer.getAuthUser—including its collision check—to reuse it. Preserve
the current first-value selection, empty-header behavior, and zero-allocation
fast path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ca6cc106-d460-4296-b664-e0be20801347
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.summanaged/cmd/pmm-managed/packages.dotis excluded by!**/*.dot
📒 Files selected for processing (49)
.golangci.yml.mockery.yamlbuild/ansible/roles/nginx/files/conf.d/pmm.confbuild/ansible/roles/nginx/files/nginx.confbuild/docker/server/entrypoint.shdashboards/dashboards/PMM Health/PMM_Health.jsondocker-compose.dev.ymlgo.modmanaged/cmd/pmm-encryption-rotation/main.gomanaged/cmd/pmm-managed/main.gomanaged/models/database.gomanaged/services/agents/deps.gomanaged/services/agents/handler.gomanaged/services/agents/handler_test.gomanaged/services/agents/mock_limiter_test.gomanaged/services/agents/registry.gomanaged/services/agents/registry_test.gomanaged/services/agents/state.gomanaged/services/agents/state_test.gomanaged/services/grafana/access_control_cache.gomanaged/services/grafana/auth_server.gomanaged/services/grafana/auth_server_bench_test.gomanaged/services/grafana/auth_server_fuzz.gomanaged/services/grafana/auth_server_fuzz_test.gomanaged/services/grafana/auth_server_test.gomanaged/services/grafana/deps.gomanaged/services/grafana/helpers.gomanaged/services/grafana/helpers_bench_test.gomanaged/services/grafana/helpers_test.gomanaged/services/grafana/mock_access_control_test.gomanaged/services/grafana/mock_grafana_auth_user_getter_test.gomanaged/services/qan/client.gomanaged/services/realtimeanalytics/deps.gomanaged/services/realtimeanalytics/mock_limiter_test.gomanaged/services/realtimeanalytics/service.gomanaged/services/realtimeanalytics/service_test.gomanaged/services/victoriametrics/victoriametrics.gomanaged/utils/interceptors/interceptors.gomanaged/utils/testdb/db.goutils/cache/cache.goutils/cache/cache_bench_test.goutils/cache/cache_test.goutils/cache/cache_ttl.goutils/cache/cache_ttl_bench_test.goutils/cache/cache_ttl_test.goutils/cache/common.goutils/rateLimiter/concurrencyLimiter.goutils/rateLimiter/concurrencyLimiter_bench_test.goutils/rateLimiter/concurrencyLimiter_test.go
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
percona/pmm-qa(manual)percona/pmm(manual)
💤 Files with no reviewable changes (1)
- managed/services/grafana/auth_server_fuzz.go
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (6)
utils/cache/cache_ttl_test.go (1)
45-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse Testify assertions in the three named files.
Replace all direct
t.Fatal*andb.Fatal*calls withrequireorassert. The cache files already importrequire; add it tomanaged/services/grafana/helpers_bench_test.go.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/cache/cache_ttl_test.go` around lines 45 - 47, The direct fatal assertion in utils/cache/cache_ttl_test.go:45-47 should use the existing Testify require import; replace all t.Fatal* and b.Fatal* calls in utils/cache/cache_ttl_test.go:45-47 and utils/cache/cache_ttl_bench_test.go:40-44 with appropriate require or assert calls. In managed/services/grafana/helpers_bench_test.go:39-45, make the same replacements and add the Testify require import.Source: Coding guidelines
managed/services/grafana/auth_server_test.go (1)
203-220: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the mocks that serve no duty.
Lines 205-211 create
candac, register anisEnabledexpectation, and assert expectations in cleanup. The subtest then callssetupLBACServer(t), which builds its own server and its own mocks. The local mocks are never attached to anything, so they only mislead the reader.♻️ Proposed cleanup
t.Run("enabled LBAC - lbacPrefixes", func(t *testing.T) { t.Parallel() - c := newMockGrafanaAuthUserGetter(t) - ac := newMockAccessControl(t) - ac.On("isEnabled").Return(true).Maybe() - t.Cleanup(func() { - c.AssertExpectations(t) - ac.AssertExpectations(t) - }) s, _, _ := setupLBACServer(t)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server_test.go` around lines 203 - 220, Remove the unused c and ac mock declarations, their isEnabled expectation, and the cleanup assertion block from the “enabled LBAC - lbacPrefixes” test; leave setupLBACServer(t) and the prefix assertions unchanged.managed/services/grafana/auth_server.go (1)
239-243: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReturn cache initialization errors from
NewAuthServer.Change the constructor to return
(*AuthServer, error)and wrap the cache error. Updatemain.go,auth_server_test.go, andauth_server_bench_test.goto handle it.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/grafana/auth_server.go` around lines 239 - 243, Update NewAuthServer to return (*AuthServer, error), wrapping and propagating failures from cache.NewCacheTTL instead of panicking, and return the initialized server with a nil error on success. Adjust callers in main.go, auth_server_test.go, and auth_server_bench_test.go to handle the constructor’s error result.Source: Coding guidelines
utils/cache/cache.go (1)
44-52: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMake it so: use the existing key hash for shard selection.
maphash.Stringalready produces the required 64-bit hash. Returnc.shards[keyHash&shardMask]and apply the same change toTTLCache.getShardto avoid the second hash on every operation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@utils/cache/cache.go` around lines 44 - 52, Update Cache.getShard to index c.shards directly with keyHash&shardMask instead of calling maphash.Comparable. Apply the same direct shard selection in TTLCache.getShard, preserving the existing precomputed hash flow.managed/services/agents/registry_test.go (1)
195-219: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer generated mocks over hand-written fakes here.
fakeHAServiceandfakeVictoriaMetricsParamsduplicate whatmockeryproduces from the interfaces inmanaged/services/agents/deps.go. This pull request already updates.mockery.yaml. Generated mocks stay in step with interface changes; hand-written fakes do not.Add
haServiceandvictoriaMetricsParamsto the mockery configuration and use the generated types.As per coding guidelines: "Generate mocks with
mockeryrather than routinely hand-rolling fakes."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry_test.go` around lines 195 - 219, Replace the hand-written fakeHAService and fakeVictoriaMetricsParams types in the registry tests with mockery-generated mocks. Add haService and victoriaMetricsParams to the mockery configuration, regenerate the mocks from the interfaces in deps.go, and update test references to use the generated types.Source: Coding guidelines
managed/services/agents/registry.go (1)
537-545: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
Collectsends to a channel while it holds a shard lock.
IterAllholds each shard's read lock for the whole iteration of that shard. The fourch <-sends inside the loop block when the Prometheus consumer is slow. While a send blocks, no goroutine can register, unregister, or kick an agent in that shard.Snapshot the metrics first, then send outside the iteration.
♻️ Proposed refactor
- for _, agent := range r.agentsCache.IterAll() { - m := agent.channel.Metrics() - - ch <- prom.MustNewConstMetric(mSentDesc, prom.CounterValue, m.Sent, agent.id) - ch <- prom.MustNewConstMetric(mRecvDesc, prom.CounterValue, m.Recv, agent.id) - ch <- prom.MustNewConstMetric(mResponsesDesc, prom.GaugeValue, m.Responses, agent.id) - ch <- prom.MustNewConstMetric(mRequestsDesc, prom.GaugeValue, m.Requests, agent.id) - } + type agentMetrics struct { + id string + m channel.Metrics + } + snapshot := make([]agentMetrics, 0, r.agentsCache.Size()) + for _, agent := range r.agentsCache.IterAll() { + snapshot = append(snapshot, agentMetrics{id: agent.id, m: agent.channel.Metrics()}) + } + for _, a := range snapshot { + ch <- prom.MustNewConstMetric(mSentDesc, prom.CounterValue, a.m.Sent, a.id) + ch <- prom.MustNewConstMetric(mRecvDesc, prom.CounterValue, a.m.Recv, a.id) + ch <- prom.MustNewConstMetric(mResponsesDesc, prom.GaugeValue, a.m.Responses, a.id) + ch <- prom.MustNewConstMetric(mRequestsDesc, prom.GaugeValue, a.m.Requests, a.id) + }Adjust the element type to the concrete return type of
channel.Channel.Metrics().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@managed/services/agents/registry.go` around lines 537 - 545, Update Registry.Collect to snapshot each agent’s metrics and identifier into a collection while iterating with agentsCache.IterAll, then perform all prom.Metric channel sends after iteration completes. Use the concrete return type of channel.Channel.Metrics() for the snapshot element type, ensuring no ch send occurs while the shard read lock is held.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@managed/services/agents/registry.go`:
- Around line 277-297: Bound the registration retry loop around LoadOrStore with
a finite attempt limit and check ctx.Done() before each retry and during failure
handling. Preserve the existing ping, AlreadyExists, and Kick behavior, while
returning the context cancellation error when the stream is cancelled and an
appropriate failure once retries are exhausted.
In `@managed/services/grafana/auth_server_test.go`:
- Around line 511-526: Resolve the disabled cache-policy checks in the auth
server tests by replacing each commented len(s.cache) assertion near
authenticateUser coverage with an active assertion using cacheSize(s). Apply the
same decision to the occurrences around the anonymous-user tests: restore
assertions that anonymous lookups leave the cache empty, or remove the comments
if that policy is no longer intended.
In `@managed/services/grafana/auth_server.go`:
- Around line 463-465: Update the no-roles branch in the relevant auth-server
method to use the struct’s `s.l` component logger instead of package-level
`logrus.Errorf`; log the user ID as a structured field and retain the existing
error return unchanged.
- Around line 344-382: Bound authentication metric labels in ServeHTTP by
deriving a route from the matched prefix returned by resolveRule and normalizing
unsupported request methods to "other". Use these bounded method and route
values for every incAuthRequests call, including parse-error, auth-error,
internal-error, and success paths, instead of raw req.Method and req.URL.Path.
In `@managed/services/grafana/helpers_test.go`:
- Around line 178-199: Update the final test-table row used by the nextPrefix
subtests so it contains an expected next-prefix value and the loop performs its
intended assertion and fuzz-corpus insertion; alternatively remove that
single-element row. Keep the existing paths and assertions unchanged for all
other rows.
In `@managed/services/grafana/helpers.go`:
- Around line 285-296: The getAuthCacheKey function must prevent different
Authorization/Cookie pairs from sharing a singleflight key. Replace the
concatenated key with a length-prefixed encoding protected by a keyed digest,
then validate the requesting headers after singleflight.Do returns before
accepting the shared authUser; add a concurrent test that exercises colliding
header pairs and confirms identities are not mixed.
In `@managed/services/victoriametrics/victoriametrics.go`:
- Around line 482-489: Update the error returns in the settings retrieval and
AddScrapeConfigs calls to wrap each underlying error with descriptive
configuration-update context using %w. Move the HA-mode comment above the
skipExternalExporter assignment onto its own line, preserving the existing
behavior.
---
Nitpick comments:
In `@managed/services/agents/registry_test.go`:
- Around line 195-219: Replace the hand-written fakeHAService and
fakeVictoriaMetricsParams types in the registry tests with mockery-generated
mocks. Add haService and victoriaMetricsParams to the mockery configuration,
regenerate the mocks from the interfaces in deps.go, and update test references
to use the generated types.
In `@managed/services/agents/registry.go`:
- Around line 537-545: Update Registry.Collect to snapshot each agent’s metrics
and identifier into a collection while iterating with agentsCache.IterAll, then
perform all prom.Metric channel sends after iteration completes. Use the
concrete return type of channel.Channel.Metrics() for the snapshot element type,
ensuring no ch send occurs while the shard read lock is held.
In `@managed/services/grafana/auth_server_test.go`:
- Around line 203-220: Remove the unused c and ac mock declarations, their
isEnabled expectation, and the cleanup assertion block from the “enabled LBAC -
lbacPrefixes” test; leave setupLBACServer(t) and the prefix assertions
unchanged.
In `@managed/services/grafana/auth_server.go`:
- Around line 239-243: Update NewAuthServer to return (*AuthServer, error),
wrapping and propagating failures from cache.NewCacheTTL instead of panicking,
and return the initialized server with a nil error on success. Adjust callers in
main.go, auth_server_test.go, and auth_server_bench_test.go to handle the
constructor’s error result.
In `@utils/cache/cache_ttl_test.go`:
- Around line 45-47: The direct fatal assertion in
utils/cache/cache_ttl_test.go:45-47 should use the existing Testify require
import; replace all t.Fatal* and b.Fatal* calls in
utils/cache/cache_ttl_test.go:45-47 and
utils/cache/cache_ttl_bench_test.go:40-44 with appropriate require or assert
calls. In managed/services/grafana/helpers_bench_test.go:39-45, make the same
replacements and add the Testify require import.
In `@utils/cache/cache.go`:
- Around line 44-52: Update Cache.getShard to index c.shards directly with
keyHash&shardMask instead of calling maphash.Comparable. Apply the same direct
shard selection in TTLCache.getShard, preserving the existing precomputed hash
flow.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: ca6cc106-d460-4296-b664-e0be20801347
⛔ Files ignored due to path filters (2)
go.sumis excluded by!**/*.summanaged/cmd/pmm-managed/packages.dotis excluded by!**/*.dot
📒 Files selected for processing (49)
.golangci.yml.mockery.yamlbuild/ansible/roles/nginx/files/conf.d/pmm.confbuild/ansible/roles/nginx/files/nginx.confbuild/docker/server/entrypoint.shdashboards/dashboards/PMM Health/PMM_Health.jsondocker-compose.dev.ymlgo.modmanaged/cmd/pmm-encryption-rotation/main.gomanaged/cmd/pmm-managed/main.gomanaged/models/database.gomanaged/services/agents/deps.gomanaged/services/agents/handler.gomanaged/services/agents/handler_test.gomanaged/services/agents/mock_limiter_test.gomanaged/services/agents/registry.gomanaged/services/agents/registry_test.gomanaged/services/agents/state.gomanaged/services/agents/state_test.gomanaged/services/grafana/access_control_cache.gomanaged/services/grafana/auth_server.gomanaged/services/grafana/auth_server_bench_test.gomanaged/services/grafana/auth_server_fuzz.gomanaged/services/grafana/auth_server_fuzz_test.gomanaged/services/grafana/auth_server_test.gomanaged/services/grafana/deps.gomanaged/services/grafana/helpers.gomanaged/services/grafana/helpers_bench_test.gomanaged/services/grafana/helpers_test.gomanaged/services/grafana/mock_access_control_test.gomanaged/services/grafana/mock_grafana_auth_user_getter_test.gomanaged/services/qan/client.gomanaged/services/realtimeanalytics/deps.gomanaged/services/realtimeanalytics/mock_limiter_test.gomanaged/services/realtimeanalytics/service.gomanaged/services/realtimeanalytics/service_test.gomanaged/services/victoriametrics/victoriametrics.gomanaged/utils/interceptors/interceptors.gomanaged/utils/testdb/db.goutils/cache/cache.goutils/cache/cache_bench_test.goutils/cache/cache_test.goutils/cache/cache_ttl.goutils/cache/cache_ttl_bench_test.goutils/cache/cache_ttl_test.goutils/cache/common.goutils/rateLimiter/concurrencyLimiter.goutils/rateLimiter/concurrencyLimiter_bench_test.goutils/rateLimiter/concurrencyLimiter_test.go
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
percona/pmm-qa(manual)percona/pmm(manual)
💤 Files with no reviewable changes (1)
- managed/services/grafana/auth_server_fuzz.go
Ticket number: PMM-15228
Percona-Lab/pmm-submodules#4481
This pull request makes significant improvements to the NGINX configuration for PMM, focusing on performance optimization, authentication and caching enhancements, and better static asset handling. It also updates code generation and linting configuration files to support new interfaces and generic types.
NGINX Configuration Improvements:
/auth_request_cachedand/auth_request_no_cache), extracting custom headers for richer error responses, and constructing JSON error payloads for 401 responses. Introduced caching for authentication responses to reduce backend load. [1] [2]STATICfor static assets andAUTH_CACHEfor authentication responses), optimizing cache usage and resource allocation.Performance and Connection Handling:
keepalive_requestsfor upstreams to handle higher loads and reduce connection churn, and added new upstream blocks forvictoriametricsandvmalertwith custom connection settings for observability data. Enabledtcp_nodelayfor low latency. [1] [2] [3]Static Asset and UI Optimization:
Route and Proxy Improvements:
^~for more precise matching, added missing HTTP/1.1 and connection headers, and improved proxy buffering settings for metrics and alerts endpoints. [1] [2]writerequests from vm-agents to VictoriaMetrics directly. Previously the scheme was the following:vm-agent -> NGINX -> vm-proxy -> VictoriaMetrics. Now it will bevm-agent -> NGINX -> VictoriaMetrics. vm-proxy is extra in this scenario, it is involved in query metrics from VictoriaMetrics but not in write metrics.PMM-ManagedImprovements:-- one pool for internal stuff (handles internal system logic like init config for VM, Check/Jobs run)
-- one for handling gRPC/REST API requests and interactions with PMM Agents. So that it doesn't interfere with internal stuff.
-- PMM Agent connection handler (agent stream connect and RTA stream connect).
-- PMM Agent State update handler.